package edu.tsp.ga.crossover;

import edu.tsp.ga.Chromosome;
import edu.tsp.ga.Gene;
import edu.tsp.ga.utils.Pair;
import edu.tsp.ga.utils.RandomGenerator;

import java.util.*;
import java.util.stream.Collectors;

public class PartiallyMatchedCrossover implements Crossover{

    @Override
    public Pair<Chromosome, Chromosome> crossover(Chromosome c1, Chromosome c2) {
        Pair<Integer, Integer> points = RandomGenerator.generateCrossPoints(c1.getGenes().size());
        Pair<List<Gene>, List<Gene>> offspring =
                partiallyMatchedCrossover(c1.getGenes(), c2.getGenes(), points.head, points.tail);
        return new Pair<>(new Chromosome(offspring.head), new Chromosome(offspring.tail));
    }

    private <T> Pair<List<T>, List<T>> partiallyMatchedCrossover(List<T> genes1, List<T> genes2, int p1, int p2) {
        if(genes1.size() != genes2.size()) {
            throw new IllegalArgumentException("PartiallyMatchedCrossover operator cannot be " +
                    "applied to two different size genes!");
        }
        Map<T, T> partialMap1 = new HashMap<>(2 * genes1.size());
        Map<T, T> partialMap2 = new HashMap<>(2 * genes2.size());
        List<T> offspring1 = new ArrayList<>(genes1);
        List<T> offspring2 = new ArrayList<>(genes1);
        if(p1 < p2) {
            for(int i = p1; i <= p2; i++) {
                partialMap1.put(genes1.get(i), genes2.get(i));
                partialMap2.put(genes2.get(i), genes1.get(i));
                offspring1.set(i, genes2.get(i));
                offspring2.set(i, genes1.get(i));
            }
        }else{
            for(int i = p1; i < genes1.size(); i++) {
                partialMap1.put(genes1.get(i), genes2.get(i));
                partialMap2.put(genes2.get(i), genes1.get(i));
                offspring1.set(i, genes2.get(i));
                offspring2.set(i, genes1.get(i));
            }
            for(int i = 0; i <= p2; i++) {
                partialMap1.put(genes1.get(i), genes2.get(i));
                partialMap2.put(genes2.get(i), genes1.get(i));
                offspring1.set(i, genes2.get(i));
                offspring2.set(i, genes1.get(i));
            }
        }
        for(int i = 0; i < genes1.size(); i++) {
            if(!isInsideRegion(i, p1, p2)) {
                T g1 = genes1.get(i);
                T g2 = genes2.get(i);
                while (partialMap2.containsKey(g1)) {
                    g1 = partialMap2.get(g1);
                }
                while (partialMap1.containsKey(g2)) {
                    g2 = partialMap1.get(g2);
                }
                offspring1.set(i, g1);
                offspring2.set(i, g2);
            }
        }
        return new Pair<>(offspring1, offspring2);
    }

    private boolean isInsideRegion(int i, int p1, int p2) {
        if(p1 < p2) {
            return i >= p1 && i <= p2;
        }else {
            return i >= p1 || i <= p2;
        }
    }

    public static void main(String args[]) {
        PartiallyMatchedCrossover crossover = new PartiallyMatchedCrossover();
        List<Integer> i1 = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7,8));
        List<Integer> i2 = new ArrayList<>(Arrays.asList(3,7,5,1,6,8,2,4));
        List<Gene> ii1 = i1.stream().map(Gene::new).collect(Collectors.toList());
        List<Gene> ii2 = i2.stream().map(Gene::new).collect(Collectors.toList());
        System.out.println(crossover.partiallyMatchedCrossover(i1,i2,3,5));
        System.out.println(crossover.partiallyMatchedCrossover(i1,i2,7,1));
        System.out.println(crossover.partiallyMatchedCrossover(ii1,ii2,3,5));
        System.out.println(crossover.partiallyMatchedCrossover(ii1,ii2,7,1));
    }

}
